package com.bangcommunity.bbframe.common.utils.id;

import java.net.NetworkInterface;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * <p>
 * Description: id生成器，可以保证id不重复和线程安全，id又当前时间，机器ip，可自增id构成。<／p> 参考mongodbid生成规则 12字节
 * 四字节时间2字节机器2字节虚拟机6字节递增数值
 * 
 * @author tanghc
 */
public class UidGenerator implements Comparable<UidGenerator>, java.io.Serializable {

    static final boolean D = false;

    /**
     * Gets a new object id.
     *
     * @return the new id
     */
    public static UidGenerator get() {
        return new UidGenerator();
    }

    /**
     * Checks if a string could be an <code>UidGenerator</code>.
     *
     * @return whether the string could be an object id
     */
    public static boolean isValid(String s) {
        if (s == null)
            return false;

        if (s.length() < 18)
            return false;

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c >= '0' && c <= '9')
                continue;
            if (c >= 'a' && c <= 'f')
                continue;
            if (c >= 'A' && c <= 'F')
                continue;

            return false;
        }

        return true;
    }

    /**
     * Turn an object into an <code>UidGenerator</code>, if possible. Strings will be converted into
     * <code>UidGenerator</code>s, if possible, and <code>UidGenerator</code>s will be cast and
     * returned. Passing in <code>null</code> returns <code>null</code>.
     *
     * @param o
     *            the object to convert
     * @return an <code>UidGenerator</code> if it can be massaged, null otherwise
     */
    public static UidGenerator massageToUidGenerator(Object o) {
        if (o == null)
            return null;

        if (o instanceof UidGenerator)
            return (UidGenerator) o;

        if (o instanceof String) {
            String s = o.toString();
            if (isValid(s))
                return new UidGenerator(s);
        }

        return null;
    }

    public UidGenerator(Date time) {
        _time = _flip((int) (time.getTime() / 1000));
        _machine = _genmachine;
        synchronized (_incLock) {
            _inc = _nextInc++;
        }
        _new = false;
    }

    public UidGenerator(Date time, int inc) {
        this(time, _genmachine, inc);
    }

    public UidGenerator(Date time, int machine, int inc) {
        _time = _flip((int) (time.getTime() / 1000));
        _machine = machine;
        _inc = inc;
        _new = false;
    }

    /**
     * Creates a new instance from a string.
     *
     * @param s
     *            the string to convert
     * @throws IllegalArgumentException
     *             if the string is not a valid id
     */
    public UidGenerator(String s) {
        this(s, false);
    }

    public UidGenerator(String s, boolean babble) {

        if (!isValid(s))
            throw new IllegalArgumentException("invalid UidGenerator [" + s + "]");

        if (babble)
            s = babbleToMongod(s);

        byte b[] = new byte[12];
        for (int i = 0; i < b.length; i++) {
            b[b.length - (i + 1)] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
        }
        ByteBuffer bb = ByteBuffer.wrap(b);

        _inc = bb.getInt();
        _machine = bb.getInt();
        _time = bb.getInt();

        _new = false;
    }

    public UidGenerator(byte[] b) {
        if (b.length != 12)
            throw new IllegalArgumentException("need 12 bytes");
        reverse(b);
        ByteBuffer bb = ByteBuffer.wrap(b);

        _inc = bb.getInt();
        _machine = bb.getInt();
        _time = bb.getInt();
    }

    public UidGenerator(int time, int machine, int inc) {
        _time = time;
        _machine = machine;
        _inc = inc;

        _new = false;
    }

    /**
     * Create a new object id.
     */
    public UidGenerator() {
        _time = _gentime;
        _machine = _genmachine;

        synchronized (_incLock) {
            _inc = _nextInc++;
        }

        _new = true;
    }

    @Override
    public int hashCode() {
        return _inc;
    }

    @Override
    public boolean equals(Object o) {

        if (this == o)
            return true;

        UidGenerator other = massageToUidGenerator(o);
        if (other == null)
            return false;

        return _time == other._time && _machine == other._machine && _inc == other._inc;
    }

    public String toStringBabble() {
        return babbleToMongod(toStringMongod());
    }

    public String toStringMongod() {
        byte b[] = toByteArray();

        StringBuilder buf = new StringBuilder(24);

        for (int i = 0; i < b.length; i++) {
            int x = b[i] & 0xFF;
            String s = Integer.toHexString(x);
            if (s.length() == 1)
                buf.append("0");
            buf.append(s);
        }

        return buf.toString();
    }

    public byte[] toByteArray() {
        byte b[] = new byte[12];
        ByteBuffer bb = ByteBuffer.wrap(b);
        bb.putInt(_inc);
        bb.putInt(_machine);
        bb.putInt(_time);
        reverse(b);
        return b;
    }

    static void reverse(byte[] b) {
        for (int i = 0; i < b.length / 2; i++) {
            byte t = b[i];
            b[i] = b[b.length - (i + 1)];
            b[b.length - (i + 1)] = t;
        }
    }

    static String _pos(String s, int p) {
        return s.substring(p * 2, (p * 2) + 2);
    }

    public static String babbleToMongod(String b) {
        if (!isValid(b))
            throw new IllegalArgumentException("invalid object id: " + b);

        StringBuilder buf = new StringBuilder(24);
        for (int i = 7; i >= 0; i--)
            buf.append(_pos(b, i));
        for (int i = 11; i >= 8; i--)
            buf.append(_pos(b, i));

        return buf.toString();
    }

    @Override
    public String toString() {
        return toStringMongod();
    }

    @Override
    public int compareTo(UidGenerator id) {
        if (id == null)
            return -1;

        long xx = id.getTime() - getTime();
        if (xx > 0)
            return -1;
        else if (xx < 0)
            return 1;

        int x = id._machine - _machine;
        if (x != 0)
            return -x;

        x = id._inc - _inc;
        if (x != 0)
            return -x;

        return 0;
    }

    public int getMachine() {
        return _machine;
    }

    public long getTime() {
        long z = _flip(_time);
        return z * 1000;
    }

    public int getInc() {
        return _inc;
    }

    public int _time() {
        return _time;
    }

    public int _machine() {
        return _machine;
    }

    public int _inc() {
        return _inc;
    }

    public boolean isNew() {
        return _new;
    }

    public void notNew() {
        _new = false;
    }

    final int _time;
    final int _machine;
    final int _inc;

    boolean _new;

    static int _flip(int x) {
        if (true) {
            byte b[] = new byte[4];
            ByteBuffer bb = ByteBuffer.wrap(b);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.putInt(x);
            bb.flip();
            bb.order(ByteOrder.BIG_ENDIAN);
            return bb.getInt();
        }
        int z = 0;
        z |= (x & 0xFF) << 24;
        z |= (x & 0xFF00) << 8;
        z |= (x & 0xFF00000) >> 8;
        z |= (x & 0xFF000000) >> 24;
        return z;
    }

    private static int _nextInc = (new java.util.Random()).nextInt();
    private static final String _incLock = new String("UidGenerator._incLock");

    private static int _gentime = _flip((int) (System.currentTimeMillis() / 1000));

    static final Thread _timeFixer;
    private static final int _genmachine;
    static {

        try {

            final int machinePiece;
            {
                StringBuilder sb = new StringBuilder();
                Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
                while (e.hasMoreElements()) {
                    NetworkInterface ni = e.nextElement();
                    sb.append(ni.toString());
                }
                machinePiece = sb.toString().hashCode() << 16;
                if (D)
                    System.out.println("machine piece post: " + Integer.toHexString(machinePiece));
            }

            final int processPiece = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode()
                    & 0xFFFF;
            if (D)
                System.out.println("process piece: " + Integer.toHexString(processPiece));

            _genmachine = machinePiece | processPiece;
            if (D)
                System.out.println("machine : " + Integer.toHexString(_genmachine));
        } catch (java.io.IOException ioe) {
            throw new RuntimeException(ioe);
        }

        _timeFixer = new Thread("UidGenerator-TimeFixer") {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(499);
                    } catch (Exception e) {
                    }
                    _gentime = _flip((int) (System.currentTimeMillis() / 1000));
                }
            }
        };
        _timeFixer.setDaemon(true);
        _timeFixer.start();
    }

    public static void main(String... args) {
        UidGenerator idGenerator = null;
        Set<String> idSet = new HashSet<String>();
        for (int i = 0; i <= 20; i++) {
            idGenerator = new UidGenerator();
            String id = idGenerator.toString();
            System.out.println(id);
            idSet.add(id);
        }
        System.out.println(idSet.size());
    }
}
